require 'ActiveHibernateInternal'
require 'rubygems'
require_gem 'builder'
require_gem 'activesupport'
require 'active_support/inflector'

module ActiveHibernate

  ManagedSessionContext = Java::org.hibernate.context.ManagedSessionContext
  Configuration = Java::org.hibernate.cfg.Configuration
  EntityNameInterceptor = Java::rubymatic.EntityNameInterceptor

  class HibernateConfig
    def initialize(config_properties)
      @hibernateCfg = Java::org.hibernate.cfg.Configuration.new
      @hibernateCfg.setInterceptor(EntityNameInterceptor.new)
      @hibernateCfg.setProperty('hibernate.connection.driver_class',config_properties[:driver])
      @hibernateCfg.setProperty('hibernate.connection.url',config_properties[:url])
      @hibernateCfg.setProperty('hibernate.connection.username',config_properties[:username])
      @hibernateCfg.setProperty('hibernate.connection.password',config_properties[:password])
      @hibernateCfg.setProperty('hibernate.dialect',config_properties[:dialect])
      # TODO use c3p0 as connectionpool
      @hibernateCfg.setProperty('hibernate.connection.pool_size','1')
      @hibernateCfg.setProperty('hibernate.current_session_context_class','managed')
      @hibernateCfg.setProperty('hibernate.cache.provider_class','org.hibernate.cache.NoCacheProvider')
      @hibernateCfg.setProperty('hibernate.current_session_context_class','managed')
      @hibernateCfg.setProperty('hibernate.show_sql','true')
      @hibernateCfg.setProperty('hibernate.jdbc.batch_size','0')
      self
    end

    def add_mapping(resourcePath)
      @hibernateCfg.addResource(resourcePath)
      self
    end

    def add_mapping_file(file)
      @hibernateCfg.addFile(file)
      self
    end
         
    def add_XML(xml_string)
      @hibernateCfg.addXML(xml_string)
      self
    end

    def create_schema
      @hibernateCfg.setProperty('hibernate.hbm2ddl.auto','create-drop')
      self
    end

    def confirm
      ActiveHibernate.set_session_factory @hibernateCfg.buildSessionFactory
    end

  end

  def self.set_session_factory(factory)
    @@factory = factory
  end

  def self.execute
    session = self.session_factory.openSession()
    begin
      ManagedSessionContext.bind(session)
      tx = session.beginTransaction()
      ret = yield session
      tx.commit()
    rescue
      tx.rollback
      raise
    ensure
      session.close
      ManagedSessionContext.unbind(@@factory)
    end
    ret
  end
  
  def self.add_mapping(resourcePath)
    @config.add_mapping(resourcePath)
  end
  
  def self.hibernator_map
    @hib_classes_map ||= {}
  end
  
  def self.default_hibernator_classes
    @default_hibernator_classes ||= {}
  end
  
  def self.set_hibernator(name)
    @current_hibernator_classes = (self.hibernator_map[name] ||= {})
  end
  
  def self.set_default_hibernator
    @current_hibernator_classes = self.default_hibernator_classes
  end
  
  def self.current_hibernator_classes
    @current_hibernator_classes ||= self.default_hibernator_classes
  end
  
  def self.establish_connection(configuration,name=nil)
    set_hibernator(name) if name
    set_default_hibernator unless name
    @connected = false
    @config = HibernateConfig.new(configuration)
  end

  def self.sf; self.session_factory; end;
  
  def self.session_factory
    self.ensure_connected2
    @@factory
  end  

  def self.ensure_connected2
    unless @connected
      self.current_hibernator_classes.each_value {|hc| hc.first_pass self.current_hibernator_classes }
      self.current_hibernator_classes.each_value {|hc| hc.second_pass self.current_hibernator_classes }
      self.current_hibernator_classes.each_value do |hc|
        visitor = XmlGenerator.new
        visitor.generate hc
        @config.add_XML visitor.xml
      end	  
      @config.create_schema.confirm
      @connected = true
    end
  end

  def self.add_hibernated_class(c)
    (self.current_hibernator_classes)[c.entity_name] = c
  end

  class PersistentRubyArray
    def method_missing(method_name, *args, &block)
      # complete list of methods split according to read/write
      if ['[]','at','each','length'].include?(method_name.to_s)
        read
      elsif ['[]=','<<','delete_if','compact!'].include?(method_name.to_s)
        write
      else
        raise "method #{method_name.to_s} not (yet) defined on the Hiberante Array wrapper"
      end
      @wrapped.send(method_name, *args, &block)
    end
  end
  
  class PersistentClass
    attr_accessor :entity_name
    attr_accessor :is_lazy
    attr_accessor :properties
    attr_accessor :many_to_ones
    attr_accessor :subclasses
    attr_accessor :joins
    attr_reader :table_name
			
    def initialize c
      @properties = []
      @many_to_ones = []
      @one_to_manys = []
      @entity_name = c.name.gsub("::",'.')
      @is_lazy = false
      @subclasses = []
      @joins = []
      @active_join = self
    end
    
    def second_pass persistentClassesByName
      @one_to_manys.each do |otm|
        otm.check_if_bidirectional persistentClassesByName
      end
    end
    
    def accept(visitor)
      Proc.new do |b|
        @properties.each do |prop|
          prop.accept(visitor).call(b)
        end
        @many_to_ones.each do |mto|
          mto.accept(visitor).call(b)
        end
        @one_to_manys.each do |otm|
          otm.accept(visitor).call(b)
        end
        @joins.each do |join|
          join.accept(visitor).call(b)
        end
        @subclasses.each do |subc|
          subc.accept(visitor).call(b)
        end
      end
    end
			
    def property_collection
      @active_join.properties
    end
    
    def add_one_to_many one_to_many
      one_to_many.hibernate_class = self
      @one_to_manys << one_to_many
    end
    
    def contains_many_to_one_to(entity_name,fk_column)
      @many_to_ones.any? {|mto| mto.entity_name==entity_name && mto.column==fk_column}
    end
    
    def many_to_one_collection
      @active_join.many_to_ones
    end
			
    def create_join(table_name,key_column)
      join = Join.new(table_name||@entity_name.split('.').join('_').upcase,key_column||super_entity_name.split('.').join('_').upcase+"_ID")
      @active_join = join
      @joins << join
    end	
			
    def reset_join
      @active_join = self
    end
  end
		
  class PersistentRootClass < PersistentClass
			
    attr_accessor :table_name
    attr_accessor :identity
			
    def initialize c
      super
      @table_name = c.name.split("::").join("_").upcase
    end
			
    def accept(visitor)
      visitor.visit_root_class(self) do |b|
        identity.accept(visitor).call(b)
        @discriminator.accept(visitor).call(b) if @discriminator            
        super.call(b)
      end
    end
			
    def first_pass persistentClassesByName; end
			
    def second_pass persistentClassesByName
      super
      if !@subclasses.empty? && !@discriminator
        @discriminator = Discriminator.new('string','TYPE')
      end
    end			
				
  end
		
  class PersistentSubClass < PersistentClass
    attr_accessor :super_entity_name
	
    def accept(visitor)
      visitor.visit_sub_class(self) do |b|
        super.call(b)
      end 
    end		
			
    def first_pass persistentClassesByName
      persistentClassesByName[@super_entity_name].subclasses << self
    end
			
    def second_pass persistentClassesByName
      super
      persistentClassesByName.delete(@entity_name)
    end  	
						
  end
		
  class Property
    attr_accessor :name
    attr_accessor :type
    attr_accessor :column
		  
    def initialize(name,type,column)
      @name = name
      @type = type
      @column = column
    end
		  
    def accept(visitor)
      visitor.visit_property(self)
    end		
  end
  
  class ManyToOne
    attr_accessor :name
    attr_accessor :entity_name
    attr_accessor :column
    
    def initialize(name,entity_name,column)
      @name = name
      @entity_name = entity_name || Inflector.camelize(name)
      @column = column || Inflector.foreign_key(@entity_name)
    end
    
    def accept(visitor)
      visitor.visit_many_to_one(self)
    end	
  end
    
  class OneToMany
    attr_accessor :name
    attr_accessor :referenced_entity_name
    attr_accessor :entity_name
    attr_accessor :fk_column
    attr_accessor :reverse
    
    def initialize(name,entity_name,fk_column)
      @name = name
      @referenced_entity_name = entity_name || Inflector.singularize(Inflector.camelize(name))
      @fk_column = fk_column
      @reverse = false
    end
    
    def hibernate_class=(hc)
      @entity_name = hc.entity_name
      @fk_column = @fk_column || Inflector.foreign_key(@entity_name)
    end
    
    def check_if_bidirectional class_map
      @reverse = class_map[@referenced_entity_name].contains_many_to_one_to(@entity_name,@fk_column)
    end  
    
    def accept(visitor)
      visitor.visit_one_to_many(self)
    end	
  end  
    
  class Discriminator
    attr_accessor :type
    attr_accessor :column
		  
    def initialize(type,column)
      @type = type
      @column = column
    end
		  
    def accept(visitor)
      visitor.visit_discriminator(self)
    end		
  end
		
  class Identity
    attr_reader :name
    attr_reader :type
    attr_reader :column
		  
    def initialize(name,type,column)
      @name = name
      @type = type
      @column = column
    end
				  
    def accept(visitor)
      visitor.visit_identity(self)
    end				  	
  end
		
  class Join
    attr_accessor :table_name
    attr_accessor :key_column
    attr_accessor :properties
    attr_accessor :many_to_ones
		  
    def initialize(table_name,key_column)
      @table_name = table_name
      @key_column = key_column
      @properties = []
      @many_to_ones = []
    end
		  
    def accept(visitor)
      visitor.visit_join(self) do |b|
        @properties.each do |prop|
          prop.accept(visitor).call(b)
        end
        @many_to_ones.each do |mto|
          mto.accept(visitor).call(b)
        end
      end
    end		
  end	
	  
  class XmlGenerator
    #  <?xml version="1.0"?>
    def initialize
      @val = <<HIB
  <!DOCTYPE hibernate-mapping PUBLIC
		"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
HIB
      @b = Builder::XmlMarkup.new(:target=>@val, :indent=>2)
    end
		  
    def generate(hc)
      @b.hibernate_mapping(&hc.accept(self))
    end
		  
    def xml
      result = @val.gsub('hibernate_mapping>','hibernate-mapping>').gsub('<many_to_one','<many-to-one').gsub('<one_to_many','<one-to-many')
      result
    end
		  
    def visit_root_class root_class
      Proc.new do |b|
        b.class(:table=>root_class.table_name,:lazy=>'false',:"entity-name"=>root_class.entity_name) do
          b.tuplizer(:"entity-mode" => 'dynamic-map', :class => 'rubymatic.RubyTuplizer')
          yield b
        end
      end
    end
    def visit_sub_class sub_class
      Proc.new do |b|
        b.subclass(:"entity-name"=>sub_class.entity_name) do
          yield b
        end
      end					
    end	
    def visit_property prop
      Proc.new do |b|
        b.property(:name => prop.name,:type => prop.type,:column => prop.column)
      end
    end
    def visit_many_to_one mto
      Proc.new do |b|
        b.many_to_one(:name => mto.name,:"entity-name" => mto.entity_name,:column => mto.column)
      end
    end
    def visit_one_to_many otm
      if otm.reverse
        inverse = "true"
      else
        inverse = "false"
      end
      Proc.new do |b|
        b.bag(:name => otm.name,:inverse => inverse,:lazy => true, :cascade => "save-update",:"collection-type" => "rubymatic.RubyArrayType") do
          b.key(:column => otm.fk_column)
          b.one_to_many(:"entity-name" => otm.referenced_entity_name)
        end
      end
    end
    def visit_join join
      Proc.new do |b|
        b.join(:table=>join.table_name) do
          b.key(:column=>join.key_column)
          yield b
        end
      end			
    end
    def visit_identity idty
      Proc.new do |b|
        b.id(:name => idty.name,:column => idty.column,:type => idty.type) {
          b.generator(:class => 'identity')
        }			  
      end				
    end
    def visit_discriminator discr
      Proc.new do |b|
        b.discriminator(:column=>discr.column,:type=>discr.type)
      end
    end
  end
    
  
end

require 'hibernator'
